package com.lingdong.onlinejudge.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.GifCaptcha;
import cn.hutool.core.lang.Validator;
import cn.hutool.extra.mail.MailUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lingdong.onlinejudge.common.ErrorCode;
import com.lingdong.onlinejudge.exception.BusinessException;
import com.lingdong.onlinejudge.mapper.UserMapper;
import com.lingdong.onlinejudge.model.dto.base.CaptchaDto;
import com.lingdong.onlinejudge.model.dto.base.EmailDto;
import com.lingdong.onlinejudge.model.dto.user.UserLoginByEmailDto;
import com.lingdong.onlinejudge.model.dto.user.UserLoginDto;
import com.lingdong.onlinejudge.model.dto.user.UserRegisterDto;
import com.lingdong.onlinejudge.model.entity.User;
import com.lingdong.onlinejudge.model.vo.base.CaptchaVo;
import com.lingdong.onlinejudge.model.vo.base.EmailVo;
import com.lingdong.onlinejudge.model.vo.user.UserLoginVo;
import com.lingdong.onlinejudge.model.vo.user.UserVo;
import com.lingdong.onlinejudge.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import static com.lingdong.onlinejudge.constant.CommonConstant.*;
import static com.lingdong.onlinejudge.constant.UserConstant.USER_LOGIN_STATE;

/**
* @author lzw
*/
@Service
@Slf4j
public class AuthServiceImpl extends ServiceImpl<UserMapper, User>
    implements AuthService {

    @Resource
    private RedisTemplate<String,String> redisTemplate;

    @Resource
    private UserMapper userMapper;

    /**
     * 用户登录
     * @param userLoginDto 用户登录信息
     * @return 用户登录信息
     */
    @Override
    public UserLoginVo userLogin(UserLoginDto userLoginDto) {
        String userAccount = userLoginDto.getUserAccount();
        String userPassword = userLoginDto.getUserPassword();
        CaptchaDto captcha = userLoginDto.getCaptcha();
        String captchaValue = captcha.getCaptchaValue();
        String captchaId = captcha.getCaptchaId();
        if (StringUtils.isAnyBlank(userAccount, userPassword, captchaValue, captchaId)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        this.checkCaptcha(captcha);
        String md5HexPassword = DigestUtils.md5Hex(userPassword);
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("user_account", userAccount);
        userQueryWrapper.eq("user_password", md5HexPassword);
        User user = userMapper.selectOne(userQueryWrapper);
        if (user == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        // 生成token
        String token = DigestUtils
                .md5Hex(user.getId() + user.getUserAccount() + USER_LOGIN_STATE + UUID.randomUUID());
        // 保存token
        redisTemplate.opsForValue()
                .set(USER_TOKEN_PREFIX + token, user.getId().toString(), USER_TOKEN_TIME, TimeUnit.SECONDS);
        // 组装返回对象
        UserLoginVo userLoginVo = new UserLoginVo();
        userLoginVo.setToken(token);
        log.info("用户登录成功，用户id为：{}", user.getId());
        return userLoginVo;
    }

    /**
     * 获取验证码
     * @return 验证码信息
     */
    @Override
    public CaptchaVo captcha() {
        String id = UUID.randomUUID().toString();
        // 生成验证码
        GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 45, 4);
        captcha.createCode();
        // 获取生成的验证码和图片
        String code = captcha.getCode();
        String image = captcha.getImageBase64();
        redisTemplate.opsForValue()
                .set(CAPTCHA_PREFIX + id, code, CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS);
        // 组装返回对象
        CaptchaVo captchaVo = new CaptchaVo();
        captchaVo.setCaptchaId(id);
        captchaVo.setCaptchaImage(image);
        log.info("验证码生成成功，验证码为：{}", code);
        return captchaVo;
    }

    /**
     * 校验验证码
     * @param captchaDto 验证码信息
     * @return 是否校验成功
     */
    @Override
    public Boolean checkCaptcha(CaptchaDto captchaDto) {
        String id = captchaDto.getCaptchaId();
        String value = captchaDto.getCaptchaValue();
        if (StringUtils.isAnyBlank(id,value)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String answer = redisTemplate.opsForValue().get(CAPTCHA_PREFIX + id);
        if (StringUtils.isBlank(answer)){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"验证码无效");
        }
        String upperCase = value.toUpperCase();
        String answerUpperCase = answer.toUpperCase();
        if (!upperCase.equals(answerUpperCase)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码错误");
        }
        redisTemplate.delete(CAPTCHA_PREFIX + id);
        return true;
    }

    /**
     * 发送邮件
     * @param email 邮箱
     * @return 结果
     */
    @Override
    public EmailVo sendEmail(String email) {
        if (!Validator.isEmail(email)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式错误");
        }
        String id = UUID.randomUUID().toString();
        try {

            String code = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
            MailUtil.send(email,
                    "online-judge 邮箱验证",
                    "<p>您的验证码为<b> "+ code + " </b>,请您在5分钟内使用验证码，过期将无法使用。打死也不要泄露给别人!<p>",
                    false);
            log.info("邮箱发送成功,{},验证码为{}", email, code);
            redisTemplate.opsForValue()
                    .set(EMAIL_PREFIX + id, code, CAPTCHA_EXPIRE_TIME , TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("邮箱发送失败,{}", email);
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱发送错误");
        }
        EmailVo emailVo = new EmailVo();
        emailVo.setEmailId(id);
        return emailVo;
    }

    /**
     * 用户登录 (邮箱验证码)
     * @param userLoginByEmailDto 用户登录信息
     * @return 用户登录信息
     */
    @Override
    public UserLoginVo userLoginByEmail(UserLoginByEmailDto userLoginByEmailDto) {
        String email = userLoginByEmailDto.getEmail();
        EmailDto emailDto = userLoginByEmailDto.getEmailDto();
        String code = emailDto.getEmailCode();
        String emailId = emailDto.getEmailId();
        if (StringUtils.isAnyBlank(email,code,emailId)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (!Validator.isEmail(email)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式错误");
        }
        this.checkEmail(emailDto);
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("email", email);
        User user = userMapper.selectOne(userQueryWrapper);
        if (user == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        // 生成token
        String token = DigestUtils
                .md5Hex(user.getId() + user.getUserAccount() + USER_LOGIN_STATE);
        // 保存token
        redisTemplate.opsForValue()
                .set(token,USER_TOKEN_PREFIX + user.getId(), USER_TOKEN_TIME, TimeUnit.SECONDS);
        // 组装返回对象
        UserLoginVo userLoginVo = new UserLoginVo();
        userLoginVo.setToken(token);
        log.info("用户登录成功，用户id为：{}", user.getId());
        return userLoginVo;
    }

    /**
     * 校验邮箱验证码
     * @param emailDto 验证码
     * @return 是否校验成功
     */
    @Override
    public Boolean checkEmail(EmailDto emailDto) {
        String id = emailDto.getEmailId();
        String value = emailDto.getEmailCode();
        if (StringUtils.isAnyBlank(id,value)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String answer = redisTemplate.opsForValue().get(EMAIL_PREFIX + id);
        if (StringUtils.isBlank(answer)){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"验证码无效");
        }
        if (!value.equals(answer)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码错误");
        }
        redisTemplate.delete(EMAIL_PREFIX + id);
        return true;
    }

    /**
     * 用户注册
     * @param userRegisterDto 用户注册信息
     * @return 是否注册成功
     */
    @Override
    public Boolean register(UserRegisterDto userRegisterDto) {
        String username = userRegisterDto.getUserName();
        String userPassword = userRegisterDto.getUserPassword();
        String checkPassword = userRegisterDto.getCheckPassword();
        String userAccount = userRegisterDto.getUserAccount();
        String email = userRegisterDto.getEmail();
        if (StringUtils.isAnyBlank(username,userPassword,checkPassword,userAccount,email)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (!Validator.isEmail(email)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式错误");
        }
        // 账号只能是12位的数字
        if (userAccount.length() != 12 || !userAccount.matches("[0-9]+")){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号格式错误");
        }
        // 密码至少8位
        if (userPassword.length() < 8){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码至少8位");
        }
        if (!userPassword.equals(checkPassword)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"两次密码不一致");
        }
        // 验证码
        CaptchaDto captchaDto = userRegisterDto.getCaptchaDto();
        if (captchaDto == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码错误");
        }
        String captchaId = captchaDto.getCaptchaId();
        String captchaValue = captchaDto.getCaptchaValue();
        if (StringUtils.isAnyBlank(captchaId,captchaValue)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        this.checkCaptcha(captchaDto);
        // 邮箱验证码
        EmailDto emailDto = userRegisterDto.getEmailDto();
        if (emailDto == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String emailId = emailDto.getEmailId();
        String emailCode = emailDto.getEmailCode();
        if (StringUtils.isAnyBlank(emailId,emailCode)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        this.checkEmail(emailDto);
        // 判断账号不能相同
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("user_account", userAccount);
        if (userMapper.selectOne(userQueryWrapper) != null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号已存在");
        }
        // 判断邮箱不能相同
        userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("email", email);
        if (userMapper.selectOne(userQueryWrapper) != null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱已注册");
        }
        User user = new User();
        BeanUtils.copyProperties(userRegisterDto,user);
        // 密码加密
        String md5HexPassword = DigestUtils.md5Hex(userPassword);
        user.setUserPassword(md5HexPassword);
        boolean save = this.save(user);
        if (!save){
            log.error("用户注册失败，用户名为：{}", user.getUserAccount());
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
        log.info("用户注册成功，用户id为：{}", user.getId());
        return true;
    }

    /**
     * 获取用户登录信息
     * @param token token
     * @return 用户信息
     */
    @Override
    public UserVo getUserInfo(String token) {
        String userId = redisTemplate.opsForValue().get(USER_TOKEN_PREFIX + token);
        if (StringUtils.isBlank(userId)){
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("id", userId);
        User user = userMapper.selectOne(userQueryWrapper);
        if (user == null){
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user,userVo);
        return userVo;
    }

    /**
     * 用户登出
     * @return 是否登出成功
     */
    @Override
    public Boolean logout(String token) {
        if (StringUtils.isAnyBlank(token)){
            return true;
        }
        Boolean delete = redisTemplate.delete(USER_TOKEN_PREFIX + token);
        if (delete == null || !delete){
            log.error("用户登出失败，token为：{}", token);
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
        return true;
    }

}




